package networking;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.concurrent.Callable;

import networking.messages.createMessages.HeatSeekerCreateMessage;
import networking.messages.createMessages.HunterCreateMessage;
import networking.messages.createMessages.ShipCreateMessage;

import logic.common.team.Team;
import logic.nodes.collision.DummyType;
import logic.ships.Ship;
import logic.ships.hunter.Hunter;
import logic.weapons.projectileWeapons.heatSeeker.HeatSeeker;
import logic.weapons.projectileWeapons.heatSeeker.HeatSeekerWeapon;
import main.InitGame;

import com.captiveimagination.jgn.JGN;
import com.captiveimagination.jgn.clientserver.JGNClient;
import com.captiveimagination.jgn.clientserver.JGNDirectConnection;
import com.captiveimagination.jgn.message.Message;
import com.captiveimagination.jgn.synchronization.message.SynchronizeCreateMessage;
import com.jme.math.Vector3f;
import com.jme.util.GameTaskQueueManager;

import factories.HunterFactory;
import gameStates.absGamesStates.network.AbsClientState;

public class ClientSyncManager extends SyncManager {
	
	public static final int CONNECT_TIME_OUT = 500;
	
	private JGNClient client;
	private InetAddress currentServerAddress;
	private AbsClientState clientState;
	private HashMap<Integer, Hunter> playerLessHunters;
	
	public ClientSyncManager(AbsClientState clientState) throws Exception {
		super(clientState);
		this.clientState = clientState;
		
		InetAddress localAddress = InetAddress.getByName(InitGame.get().getThisIP());
		InetSocketAddress clientReliable = new InetSocketAddress(localAddress, 0);
		InetSocketAddress clientFast = new InetSocketAddress(localAddress, 0);
		
		client = new JGNClient(clientReliable, clientFast);
		client.addServerConnectionListener(clientState);
		client.addMessageListener(clientState);
		JGN.createThread(client).start();
		
		playerLessHunters = new HashMap<Integer, Hunter>();
		
		initSyncManager();
	}
	
	public void connectToServer(InetAddress serverAddress) throws Exception {
		if(currentServerAddress != null 
		&& currentServerAddress.getHostAddress().equals(serverAddress.getHostAddress())) return;
		
		InetSocketAddress serverReliable = new InetSocketAddress(serverAddress, TCPPORT);
		InetSocketAddress serverFast = new InetSocketAddress(serverAddress, UDPPORT);
		
		assert(!isConnected());
		
		currentServerAddress = serverAddress;
		client.connectAndWait(serverReliable, serverFast, CONNECT_TIME_OUT);
		
		JGNDirectConnection realServerConnection = client.getServerConnection();
		if(realServerConnection != null) 
			realServerConnection.getReliableClient().addMessageListener(clientState);
	}
	
	public void endClient() {
		if(!isConnected()) return;
		
		try {
			graphicSyncManager.shutdown();
			client.disconnect();
			client.close();
			while(client.isAlive()) {}
		} catch(Exception e) {
			
		} finally {
			cleanup();
			graphicController = null;
			graphicSyncManager = null;
			client = null;
		}
	}
	
	public boolean isConnected() { return client.getServerConnection() != null; }
	
	public void sendMessageToServer(Message message) {
		if(isConnected()) client.sendToServer(message);
	}
	
	@Override
	protected SynchronizationManager getNewSyncManager() {
		return new SynchronizationManager(client, graphicController);
	}
	
	@Override
	public Object create(SynchronizeCreateMessage message) {
		if(message == null || ingameState == null) return null;
		if(message instanceof ShipCreateMessage) {
			if(ingameState.getGame() == null) return null;
			
			final ShipCreateMessage scm = (ShipCreateMessage)message;
			if(scm.getFraction() == null) return null;
			final Team team = ingameState.getGame().getTeam(scm.getFraction());
			
			if(scm.getType() == null) return null;
			
			try {
				return GameTaskQueueManager.getManager().update(new Callable<Ship>() {
					@Override
					public Ship call() throws Exception {
						Ship newShip = null;
						
						switch(scm.getType()) {
							case Hunter: {
								HunterCreateMessage hcm = (HunterCreateMessage)scm;
								String hunterName = hcm.getHunterName();
								newShip = HunterFactory.createNewHunter(hunterName, team, DummyType.Full);
								Hunter hunter = (Hunter)newShip;
																
								putPlayerToHunter(hunter, hcm.getPlayerID());
								break;
							}
							case Drone: newShip = team.addNewDrone(DummyType.Full); break;
							case Carrier: newShip = team.addNewCarrier(DummyType.Full); break;
							case Frigate: newShip = team.addNewFrigate(DummyType.Full); break;
						}
						
						if(newShip != null) {
							newShip.setID(scm.getShipID());
							assert(scm.getShipID() > 0);
							clientState.registerTransmittedShip(newShip);
							ingameState.addNode(newShip);
						}
						return newShip;
					}
				}).get();
			} catch(Exception e) { e.printStackTrace(); }
		} else if(message instanceof HeatSeekerCreateMessage) {
			final HeatSeekerCreateMessage hscm = (HeatSeekerCreateMessage)message;
			if(hscm.getClientWeaponFireID() != 0) 
				return clientState.getFiredHeatSeeker(hscm.getClientWeaponFireID());
			
			try {
				return GameTaskQueueManager.getManager().update(new Callable<HeatSeeker>() {
					@Override
					public HeatSeeker call() throws Exception {
						int weaponID = hscm.getWeaponID();
						HeatSeekerWeapon weapon = (HeatSeekerWeapon)clientState.getWeapon(weaponID);
						if(weapon == null) return null;
						
						HeatSeeker hs = (HeatSeeker)weapon.fire(Vector3f.UNIT_Z, true);
						ingameState.addNode(hs);
						clientState.registerWeaponFire(hs, hscm.getHeatSeekerID());
						return hs;
					}
				}).get();
			} catch(Exception e) { e.printStackTrace(); }
		}
		return null;
	}
	
	@Override
	public void putPlayerToHunter(Hunter hunter, int playerID) {
		if(networkState.getPlayer(playerID) == null) playerLessHunters.put(new Integer(playerID), hunter);
		else super.putPlayerToHunter(hunter, playerID);
	}
	
	public void checkPlayerLessHunters(int playerID) {
		Hunter hunter = playerLessHunters.get(Integer.valueOf(playerID));
		if(hunter != null) putPlayerToHunter(hunter, playerID);
	}
}